﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.FindSymbols;
using Microsoft.CodeAnalysis.MSBuild;
using RoslynEvalApp.EvalTargets;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace RoslynEvalApp.Models.Services
{
    class RoslynEvalService : IService
    {
        private MSBuildWorkspace workspace;
        private MSBuildWorkspace Workspace
        {
            get
            {
                if(workspace == null)
                {
                    workspace = MSBuildWorkspace.Create();
                }

                return workspace;
            }
        }

        public void Execute(object args)
        {
            OtherA oa = new OtherA();
            oa.OtherMethodDerived1A();

            string resultString = EvalImpl();
            Debug.WriteLine(resultString);
        }

        private string EvalImpl()
        {
            string solutionFullPath = this.GetSolutionFullPath();
            Solution solution = this.OpenSolutionSync(solutionFullPath);

            string projectFullPath = this.GetProjectFullPath();
            var projectSync = this.OpenProjectSync(projectFullPath);

            var compilationSync = this.GetCompilationSync(projectSync);

            StringBuilder builder = new StringBuilder();
            string[] metaDataNames = this.GetTargetMetadataNames();
            foreach (var metadataName in metaDataNames)
            {
                var typeSymbol = compilationSync.GetTypeByMetadataName(metadataName);
                var methodSymbol = GetMethodSymbol(typeSymbol);
                var overrideSymbols = this.FindOverridesSync(methodSymbol, solution);
                var implSymbols = this.FindImplementationsSync(methodSymbol, solution);
                var refsSymbols = this.FindReferencesSync(methodSymbol, solution);
                var callerInfos = this.FindCallersSync(methodSymbol, solution);

                builder.AppendLine(typeSymbol.ToDisplayString());
                builder.AppendLine(SymbolsToStringGenerics(overrideSymbols, "=== Symbol Overriding the base method === ", SymbolToString));
                builder.AppendLine(SymbolsToStringGenerics(implSymbols, "=== Symbol Implementing the interface === ", SymbolToString));
                builder.AppendLine(SymbolsToStringGenerics(refsSymbols, "=== Symbol Referenced === ", ReferencedSymbolToString));
                builder.AppendLine(SymbolsToStringGenerics(callerInfos, "=== Caller Info === ", CallerInfoToString));

                builder.AppendLine("========================================================");
            }

            return builder.ToString();
        }

        private IMethodSymbol GetMethodSymbol(INamedTypeSymbol typeSymbol)
        {
            string methodName = "MethodA";

            IMethodSymbol symbol = typeSymbol.GetMembers(methodName).Where(sym => sym.Kind == SymbolKind.Method).FirstOrDefault() as IMethodSymbol;

            return symbol;
        }

        private string SymbolsToStringGenerics<T>(IEnumerable<T> symbols, string header, Func<T, string, string> symbolToString)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var symbol in symbols)
            {
                string traceout = symbolToString(symbol, header);
                builder.Append(traceout);
            }

            return builder.ToString();
        }

        private string ReferencedSymbolsToString(IEnumerable<ReferencedSymbol> refedSymbols, string header)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var symbol in refedSymbols)
            {
                string traceout = ReferencedSymbolToString(symbol, header);
                builder.Append(traceout);
            }

            return builder.ToString();
        }

        private string ReferencedSymbolToString(ReferencedSymbol refedSymbol, string header)
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendLine(SymbolToString(refedSymbol.Definition, header));

            foreach (var location in refedSymbol.Locations)
            {
                var semanticModelAsync = location.Document.GetSemanticModelAsync();
                ISymbol referringSymbol = semanticModelAsync.Result.GetEnclosingSymbol(location.Location.SourceSpan.Start);

                builder.Append("-> referring symbol : ");
                builder.AppendLine(referringSymbol.ToDisplayString());
            }

            return builder.ToString();
        }

        private string CallerInfoToString(SymbolCallerInfo callerInfo, string header)
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendLine(header);
            builder.AppendLine(SymbolToString(callerInfo.CalledSymbol, "=== Symbol Called === "));
            builder.AppendLine(SymbolToString(callerInfo.CallingSymbol, "=== Symbol Calling === "));

            foreach (var location in callerInfo.Locations)
            {
                var line = location.GetLineSpan().StartLinePosition.Line;

                builder.Append("-> line : ");
                builder.AppendLine(line.ToString());
            }

            return builder.ToString();
        }


        private string SymbolsToString(IEnumerable<ISymbol> symbols, string header)
        {
            StringBuilder builder = new StringBuilder();
            foreach (var symbol in symbols)
            {
                string traceout = SymbolToString(symbol, header);
                builder.Append(traceout);
            }

            return builder.ToString();
        }

        private string SymbolToString(ISymbol symbol, string header)
        {
            StringBuilder builder = new StringBuilder();
            builder.AppendLine(header);

            // 次期C#では、NameOf operatorがサポートされ、コーディングしたメンバ名をそのまま出力することができるみたい
            builder.Append("ToDisplayString:").AppendLine(symbol.ToDisplayString());

            return builder.ToString();
        }

        // パス等の環境情報関係はリソース関係の別クラスへ
        private string GetSolutionFullPath()
        {
            string slnDir = @"C:\Users\takashi\Documents\takashi\CodePlex\RoslynEval\RoslynEval";
            string slnName = @"RoslynEval.sln";
            string solutionFullPath = Path.Combine(slnDir, slnName);

            return solutionFullPath;
        }

        private string GetProjectFullPath()
        {
            string projDir = @"C:\Users\takashi\Documents\takashi\CodePlex\RoslynEval\RoslynEval\RoslynEvalApp";
            string projName = @"RoslynEvalApp.csproj";

            string projectFullPath = Path.Combine(projDir, projName);

            return projectFullPath;
        }

        private string[] GetTargetMetadataNames()
        {
            string[] metaDataNames = new[]
            {
                typeof(InterfaceX).FullName,
                typeof(Impl1A).FullName,
                typeof(Impl2A).FullName,
                typeof(BaseA).FullName,
                typeof(Derived1A).FullName,
                typeof(Derived1B).FullName,
                typeof(Derived1C).FullName,
                typeof(Derived2A1).FullName,
                typeof(Derived2A2).FullName,
                typeof(Derived2B).FullName,
                typeof(Derived2C).FullName
            };

            return metaDataNames;
        }

        private Solution OpenSolutionSync(string solutionFullPath)
        {
            var solutionAsync = this.Workspace.OpenSolutionAsync(solutionFullPath);
            return solutionAsync.Result;
        }

        private Project OpenProjectSync(string projectFullPath)
        {
            var projectAsync = workspace.OpenProjectAsync(projectFullPath);
            return projectAsync.Result;
        }

        private Compilation GetCompilationSync(Project project)
        {
            var compilationAsync = project.GetCompilationAsync();
            return compilationAsync.Result;
        }

        private IEnumerable<ISymbol> FindOverridesSync(ISymbol symbol, Solution solution)
        {
            var overrideSymbols = SymbolFinder.FindOverridesAsync(symbol, solution);
            return overrideSymbols.Result;
        }

        private IEnumerable<ISymbol> FindImplementationsSync(ISymbol symbol, Solution solution)
        {
            var implSymbols = SymbolFinder.FindImplementationsAsync(symbol, solution);
            return implSymbols.Result;
        }

        private IEnumerable<ReferencedSymbol> FindReferencesSync(ISymbol symbol, Solution solution)
        {
            var refsSymbols = SymbolFinder.FindReferencesAsync(symbol, solution);
            return refsSymbols.Result;
        }

        private IEnumerable<SymbolCallerInfo> FindCallersSync(ISymbol symbol, Solution solution)
        {
            var callerSymbols = SymbolFinder.FindCallersAsync(symbol, solution);
            return symbol != null ? callerSymbols.Result : new SymbolCallerInfo[0];
        }
    }
}
